<template>
  <div style="height: 100%;display: flex;flex-direction: column;overflow: hidden">
    <div style="height: 60px;display: flex;align-items: center;justify-content: space-between">
      <div style="margin-left: 0px">
        <span style="font-size: 23px;color: #778496">图数据探查</span>
      </div>
      <div
        style="background-color: white;padding-left: 8px;padding-right: 8px;border-radius: 6px;display: inline-flex;align-items: center">
        <icon-font slot="prefix" :style="{ 'font-size':'19px','color':'#778496'}" type="icon-search"></icon-font>
        <input
          @keyup.enter="onSearch"
          class="graph_search"
          placeholder="Search..." />
      </div>
    </div>
    <div style="flex: 1;background-color: white;display: flex;flex-direction: column" id="graph_container_root">
      <div style="height: 50px;display: flex;align-items: center">
        <div style="margin-left: 12px;flex: 1;">
          <Legend
            :data="legends"
            @onItemChick="legendChange"
          ></Legend>
        </div>
        <div
          style="margin-left: 12px;margin-right: 12px;text-align: center">
          <a-tooltip placement="top" title="停止渲染">
            <img @click.stop="clickStop" alt="" width="26" height="26" src="~@/assets/icons/icon_stop_render.png">
          </a-tooltip>
          <a-tooltip v-if="enableFullscreen" placement="top" title="全屏显示" style="margin-left: 16px">
            <img @click.stop="clickFullscreen" alt="" width="24" height="24" src="~@/assets/icons/icon_full_screen.png">
          </a-tooltip>
          <a-tooltip placement="top" title="统计图" style="margin-left: 16px">
            <img @click.stop="clickStatistics" alt="" width="24" height="24" src="~@/assets/icons/icon_statistics.png">
          </a-tooltip>
          <a-tooltip placement="top" title="设置" style="margin-left: 16px">
            <img
              id="graphSetting"
              @click.stop="clickSetting"
              alt=""
              width="26"
              height="26"
              src="~@/assets/icons/icon_setting.png">
          </a-tooltip>
        </div>
      </div>
      <div style="flex: 1;position: relative">
        <div
          id="index_graph_container"
          style="position: absolute;left: 12px;top: 0;right: 12px;bottom: 0;">
        </div>
        <div v-if="confirmLoading" style="width: 100%;height: 100%;z-index: 1000;text-align: center;">
          <a-spin style="margin-top: 200px" size="large"></a-spin>
        </div>
      </div>
    </div>
    <graph-detail
      @close="showJson=false"
      :content="jsonValue"
      :title="graphDetailTitle"
      :position-y="menuPositionY"
      :position-x="menuPositionX"
      v-if="showJson">
    </graph-detail>
    <transition name="slide-fade">
      <graph-setting
        :x="settingX"
        :y="settingY"
        :option="settingCfg"
        @close="visibleSetting=false"
        v-if="visibleSetting"
        @graphSetting="graphSettingChange">
      </graph-setting>
    </transition>
    <transition name="slide-fade">
      <graph-chart
        @close="showStatistics=false"
        :nodes="graph.cfg.data.nodes"
        :edges="graph.cfg.data.edges"
        :category="legends"
        :x="chartPositionX"
        :y="chartPositionY"
        v-if="showStatistics">
      </graph-chart>
    </transition>
    <div
      :style="{'position':'absolute','left':menuContainerX+'px','top':menuContainerY+'px','z-index':1002}">
      <div style="padding: 5px;background-color: #fff;border-radius: 5px;height: 30px">
        <span style="user-select: none;cursor: pointer" @click="showGraphDetail">查看详情</span>
      </div>
    </div>
  </div>
</template>

<script>
import { listConceptWithAttrs } from '@/api/modular/main/knowl/schema/schemaManage'
import { graphPreviewQuery } from '@/api/modular/main/knowl/graph_preview/graphPreviewManager'
import G6 from '@antv/g6'
import Legend from '@/components/Legend'
import { rgb2hex } from '@/utils/colorUtil'
import GraphSetting from '@/views/main/knowl/dolphinx/GraphSetting'
import GraphChart from '@/views/main/knowl/dolphinx/GraphChart'
import GraphDetail from '@/views/main/knowl/dolphinx/GraphDetail'
import screenfull from 'screenfull'
import CloseLayer from '@/components/CloseLayer/CloseLayer'

export default {
  components: {
    CloseLayer,
    GraphDetail,
    GraphChart,
    GraphSetting,
    Legend
  },
  data() {
    return {
      queryParam: {},
      settingCfg: {},
      enableFullscreen: false,
      graph: undefined,
      confirmLoading: false,
      nameMapping: undefined,
      limit: 1,
      randomColorMap: undefined,
      legends: [],
      showStatistics: false,
      menuContainerX: -100,
      menuContainerY: -100,
      menuPositionX: -100,
      menuPositionY: -100,
      chartPositionX: 0,
      chartPositionY: 0,
      settingX: 0,
      settingY: 0,
      showJson: false,
      jsonValue: undefined,
      nodeEnv: undefined,
      graphDetailTitle: undefined,
      visibleSetting: false,
      showText: true,
      duration: undefined,
      stopRender: false,
      startNode: undefined
    }
  },

  beforeDestroy() {
    window.removeEventListener('resize', this.onResizeChange)
    this.destroyGraph()
    if (this.duration) {
      clearTimeout(this.duration)
    }
  },
  mounted() {
    this.enableFullscreen = screenfull.isEnabled
    window.addEventListener('resize', this.onResizeChange)
    this.duration = setTimeout(() => {
      this.initGraph()
      this.listConceptAttrs()
    }, 120)
  },
  methods: {
    destroyGraph() {
      if (this.graph) {
        this.graph.off()
        try {
          this.graph.clear()
          this.graph.destroy()
        } catch (e) {
          console.log(e)
        }
      }
      this.graph = undefined
    },
    initGraph() {
      const cfg = this.getConfig()
      this.graph = new G6.Graph(cfg)
      this.graph.on('canvas:click', () => {
        this.hiddenContextMenu()
        this.clearAllStats()
      })
      this.graph.on('node:mouseleave', this.clearAllStats)
      this.graph.on('node:mouseenter', this.changeHighlight)

      this.graph.on('edge:mouseleave', this.clearAllStats)
      this.graph.on('edge:mouseenter', this.changeEdgeHighlight)

      this.graph.on('node:contextmenu', this.showContextMenu)
      this.graph.on('edge:contextmenu', this.showContextMenu)
    },
    getConfig() {
      const graphContainer = document.getElementById('index_graph_container')
      const graphRect = graphContainer.getBoundingClientRect()
      const tooltip = new G6.Tooltip({
        getContent(e) {
          const model = e.item.getModel()
          if (!model) {
            return
          }
          return `<div> ${model.label || model.edgeLabel || model.nodeLabel || model.id} </div>`
        },
        itemTypes: ['node', 'edge']
      })
      return {
        container: 'index_graph_container',
        width: graphRect.width,
        height: graphRect.height,
        fitView: false,
        animate: false,
        plugins: [tooltip],
        defaultNode: {
          labelCfg: {
            style:
              {
                fill: '#333333',
                fontSize: 12
              },
            position: 'top'
          }
        },
        nodeStateStyles: {
          highlight: {
            opacity: 1
          },
          dark: {
            opacity: 0.2
          }
        },
        modes: {
          // 支持的 behavior
          default: ['drag-canvas', 'zoom-canvas', 'drag-node']
        },
        layout: {
          type: 'gForce',
          onLayoutEnd: () => {
            this.moveToCenter()
          }
        }
      }
    },
    showGraphDetail() {
      this.showStatistics = false
      this.visibleSetting = false
      this.hiddenContextMenu()
      if (this.showJson) {
        this.showJson = false
      }
      this.setGraphConfig(this.nodeEnv)
      this.$nextTick(() => {
        this.showJson = true
      })
    },
    graphSettingChange(cfg) {
      this.settingCfg = Object.assign(cfg)
      this.showText = cfg['visibleText']
      this.visibleEdgeTextChange()
      const deep = cfg['deep']
      const limit = cfg['limit']
      this.queryParam.limit = limit && limit.length > 0 ? parseInt(limit) : undefined
      this.queryParam.deep = deep && deep.length > 0 ? parseInt(deep) : undefined
      this.loadGraphPreview()
    },
    hiddenContextMenuByNode(e) {
      if (!this.jsonValue) {
        return
      }
      const item = e.item
      try {
        const id = item._cfg.id
        const selectNode = JSON.parse(this.jsonValue)
        if (selectNode) {
          if (selectNode['id'] !== id) {
            this.hiddenContextMenu()
          }
        }
      } catch (e) {
        console.log(e)
      }
    },
    hiddenContextMenu() {
      this.menuContainerX = -1000
      this.menuContainerY = -1000
    },
    getTitleByNodeModel(model) {
      return `${model.label || model.edgeLabel || model.nodeLabel || model.id}`
    },
    setGraphConfig(env) {
      const maxHeight = document.documentElement.clientHeight
      const detailViewHeight = 400
      let offsetY = env.clientY
      if (maxHeight - env.clientY < detailViewHeight) {
        offsetY = (env.clientY + detailViewHeight - maxHeight)
      }
      const model = env.item.getModel()
      this.jsonValue = model ? JSON.stringify(model, null, 4) : ''
      this.graphDetailTitle = this.getTitleByNodeModel(model)
      this.menuPositionY = offsetY
      this.menuPositionX = env.clientX
    },
    showContextMenu(env) {
      env.preventDefault()
      env.stopPropagation()
      this.nodeEnv = env
      this.menuContainerY = env.clientY
      this.menuContainerX = env.clientX
    },
    onResizeChange() {
      const graphContainer = document.getElementById('index_graph_container')
      const graphRect = graphContainer.getBoundingClientRect()
      if (graphRect.width && this.graph) {
        this.graph.changeSize(graphRect.width, graphRect.height)
      }
    },
    clickStop() {
      if (this.graph) {
        this.stopRender = true
        this.graph.destroyLayout()
      }
    },
    clickFullscreen() {
      const element = document.getElementById('graph_container_root')
      try {
        screenfull.toggle(element)
      } catch (e) {
        console.log(e)
      }
    },
    clickStatistics(v) {
      this.showJson = false
      this.visibleSetting = false
      this.chartPositionY = v.clientY + 24
      // contentWidth imageWidth  marginLeft
      this.chartPositionX = v.clientX - 605 + 26 + 32
      this.$nextTick(() => {
        this.showStatistics = !this.showStatistics
      })
    },
    clickSetting(v) {
      console.log(v)
      this.settingY = v.clientY + 24
      this.settingX = v.clientX - 345
      this.showJson = false
      this.showStatistics = false
      this.$nextTick(() => {
        this.visibleSetting = !this.visibleSetting
      })
    },
    legendChange(item) {
      this.visibleNode(item.key, item.selected)
    },
    visibleNode(nodeId, visible) {
      const data = this.graph.cfg.data
      data.nodes.forEach((it) => {
        if (it.id.startsWith(nodeId)) {
          const node = this.graph.findById(it.id)
          if (node) {
            if (visible) {
              this.graph.showItem(node, false)
            } else {
              this.graph.hideItem(node, false)
            }
          }
        }
      })
    },
    visibleEdgeTextChange() {
      const data = this.graph.cfg.data
      data.nodes.forEach((node) => {
        if (this.showText) {
          node['label'] = node['nodeLabel']
        } else {
          node['label'] = ''
        }
      })
      data.edges.forEach((node) => {
        if (this.showText) {
          node['label'] = node['edgeLabel']
        } else {
          node['label'] = ''
        }
      })
      this.graph.changeData(data)
    },
    changeHighlight(e) {
      const item = e.item
      this.hiddenContextMenuByNode(e)
      this.graph.getNodes().forEach((node) => {
        this.graph.clearItemStates(node)
        this.graph.setItemState(node, 'dark', true)
      })
      this.graph.setItemState(item, 'dark', false)
      this.graph.setItemState(item, 'highlight', true)
      this.updateNodeLabelColor(item, '#1E90FF')
      this.graph.getEdges().forEach((edge) => {
        if (edge.getSource() === item) {
          this.graph.setItemState(edge.getTarget(), 'dark', false)
          this.graph.setItemState(edge.getTarget(), 'highlight', true)
          this.graph.setItemState(edge, 'highlight', true)
          edge.toFront()
          this.updateNodeLabelColor(edge.getTarget(), '#1E90FF')
          this.updateNodeLabelColor(edge, '#1E90FF')
        } else if (edge.getTarget() === item) {
          this.graph.setItemState(edge.getSource(), 'dark', false)
          this.graph.setItemState(edge.getSource(), 'highlight', true)
          this.graph.setItemState(edge, 'highlight', true)
          this.updateNodeLabelColor(edge.getSource(), '#1E90FF')
          this.updateNodeLabelColor(edge, '#1E90FF')
          edge.toFront()
        } else {
          this.graph.setItemState(edge, 'highlight', false)
        }
      })
      this.graph.paint()
      this.graph.setAutoPaint(true)
    },
    updateNodeLabelColor(item, color) {
      const cfg = {
        labelCfg: {
          style: {
            fill: color
          }
        }
      }
      this.graph.updateItem(item, cfg, false)
    },
    changeEdgeHighlight(e) {
      const item = e.item
      this.hiddenContextMenuByNode(e)
      this.graph.setAutoPaint(false)
      this.graph.getNodes().forEach((node) => {
        this.graph.clearItemStates(node)
        this.graph.setItemState(node, 'dark', true)
      })
      this.graph.getEdges().forEach((edge) => {
        this.graph.clearItemStates(edge)
        this.graph.setItemState(edge, 'dark', true)
      })
      const sourceNode = item.getSource()
      const targetNode = item.getTarget()
      this.updateNodeLabelColor(item, '#1E90FF')
      this.updateNodeLabelColor(sourceNode, '#1E90FF')
      this.updateNodeLabelColor(targetNode, '#1E90FF')
      this.graph.setItemState(sourceNode, 'highlight', true)
      this.graph.setItemState(targetNode, 'highlight', true)
      this.graph.setItemState(item, 'highlight', true)
      this.graph.paint()
      this.graph.setAutoPaint(true)
    },
    clearAllStats() {
      this.graph.setAutoPaint(false)
      this.graph.getNodes().forEach((node) => {
        this.graph.clearItemStates(node)
        this.updateNodeLabelColor(node, '#333')
      })
      this.graph.getEdges().forEach((edge) => {
        this.graph.clearItemStates(edge)
        this.updateNodeLabelColor(edge, '#333')
      })
      this.graph.paint()
      this.graph.setAutoPaint(true)
    },
    onSearch(v) {
      const search = v.target.value
      this.showStatistics = false
      this.queryParam.searchValue = search
      this.loadGraphPreview()
    },
    moveToCenter() {
      if (this.startNode) {
        // const canvas = this.graph.get('canvas')
        // const canvasCenter = {
        //   x: canvas.cfg.width / 2,
        //   y: canvas.cfg.height / 2
        // }
        const width = this.graph.get('width')
        const height = this.graph.get('height')
        const viewCenter = {
          x: width / 4,
          y: height / 3
        }
        this.graph.translate(viewCenter.x, viewCenter.y)
      }
    },
    pageChange() {
      this.loadGraphPreview()
    },
    getColorRandom(num) {
      const colorArray = []
      while (colorArray.length < num) {
        const r = Math.floor(Math.random() * 256)
        const g = Math.floor(Math.random() * 256)
        const b = Math.floor(Math.random() * 256)
        const color = rgb2hex(r, g, b)
        const find = colorArray.find((it) => it === color)
        if (!find) {
          colorArray.push(color)
        }
      }
      return colorArray
    },
    loadGraphPreview() {
      this.confirmLoading = true
      graphPreviewQuery(Object.assign({}, this.queryParam)).then((res) => {
        const data = res.data
        if (!data.edges) {
          data.edges = []
        }
        if (!data.vertexs) {
          data.vertexs = []
        }
        if (!data.schemaList) {
          data.schemaList = []
        }
        const tempLegend = []
        data.schemaList.forEach((it) => {
          const tagColor = this.randomColorMap.get(it.synonym)
          tempLegend.push({
            name: it.name,
            key: it.synonym,
            selected: true,
            selectedColor: tagColor,
            unSelectColor: '#999',
            shapeSize: 12
          })
        })
        this.legends = tempLegend
        data.edges.forEach((it) => {
          it['source'] = it.from
          it['target'] = it.to
          if (this.showText) {
            it['label'] = it.properties.name
          }
          it['edgeLabel'] = it.properties.name
          it['labelCfg'] = {
            'style':
              {
                'fill': '#999999'
              }
          }
          it['style'] = {
            endArrow: {
              path: G6.Arrow.triangle(8, 10, 1),
              d: 1,
              fill: '#999'
            },
            stroke: '#999',
            lineWidth: 1
          }
        })
        this.startNode = data.startNode
        if (data.startNode) {
          data.startNode['startNode'] = true
          data.vertexs.push(data.startNode)
        }
        data.vertexs.forEach((it) => {
          const tempId = it.id
          const namePrefix = tempId.substring(0, tempId.indexOf('/'))
          const cacheEntity = this.nameMapping.get(namePrefix)
          if (cacheEntity) {
            const pps = cacheEntity.name
            it['nodeLabel'] = it.properties[pps]
            if (this.showText) {
              it['label'] = it.properties[pps]
            }
          } else {
            it['nodeLabel'] = it.key
            if (this.showText) {
              it['label'] = it.key
            }
          }
          it['type'] = 'circle'
          it['size'] = it['startNode'] ? 30 : 16
          let colorCc = this.randomColorMap.get(namePrefix)
          if (!colorCc) {
            colorCc = this.getColorRandom(1)[0]
            this.randomColorMap.set(namePrefix, colorCc)
          }
          it['style'] = {
            fill: colorCc,
            stroke: 'rgb(255,255,255)',
            lineWidth: it['startNode'] ? 2 : '0'
          }
        })
        const renderData = {
          edges: data.edges,
          nodes: data.vertexs
        }
        if (this.stopRender) {
          this.stopRender = false
          this.destroyGraph()
          this.initGraph()
        }
        this.graph.data(renderData)
        this.graph.render()
      }).catch((e) => {
        console.log(e)
      }).finally(() => {
          this.confirmLoading = false
        }
      )
    },
    listConceptAttrs() {
      listConceptWithAttrs()
        .then((res) => {
          this.nameMapping = new Map()
          this.randomColorMap = new Map()
          const conceptList = res.data
          const colorArray = this.getColorRandom(conceptList.length)
          for (let i = 0; i < conceptList.length; i++) {
            const oo = conceptList[i]
            this.randomColorMap.set(oo.synonym, colorArray[i])
          }
          conceptList.forEach((it) => {
            const prop = it.propertys.find((i) => i.isShowCol === '是')
            if (prop) {
              this.nameMapping.set(it['synonym'], {
                'name': prop.enName
              })
            }
          })
          this.loadGraphPreview()
        }).catch((e) => {
        console.log(e)
      })
    }
  }
}
</script>

<style scoped lang="less">

.slide-fade-enter-active {
  transition: all .1s ease;
}

.slide-fade-leave-active {
  transition: all .1s cubic-bezier(1.0, 0.5, 0.5, 1.0);
}

.slide-fade-enter, .slide-fade-leave-to {
  transform: translateX(10px);
  opacity: 0;
}

.graph_search {
  height: 40px;
  margin-left: 3px;
  margin-right: 3px;
  width: 330px;
  border: 0;
  outline: none;
  color: #333;
  font-size: 13px;
}

.graph_search::placeholder {
  color: #778496;
  font-size: 13px;
}
</style>
